﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Reflection;

namespace Wosperry.ExpressionExtensions
{
    /// <summary>
    /// 动态构建表达式拓展类
    /// </summary>
    public static class BuildWhereExtensions
    {
        /// <summary>
        /// 根据传入实体构建Lambda表达式
        /// </summary>
        /// <typeparam name="TSource">Queryable实体类型</typeparam>
        /// <typeparam name="TQueryParams">对比的参数对象类型</typeparam>
        /// <param name="_">为IQueryable拓展，丢弃</param>
        /// <param name="queryObject">对比的参数对象</param>
        /// <param name="options">配置项，仅支持StringComparison</param>
        /// <returns></returns>
        public static Expression<Func<TSource, bool>>
            BuildLambda<TSource, TQueryParams>(this IQueryable<TSource> _, TQueryParams queryObject, Action<BuildWhereOptions> options = null)
            where TSource : class
            where TQueryParams : class
        {
            // 初始化配置项
            var buildWhereOptions = new BuildWhereOptions();
            if (!(options is null))
            {
                options(buildWhereOptions);
            }

            // 表达式参数 t
            var t = Expression.Parameter(typeof(TSource), "t");

            // TODO：把 `true && true` 替换成更合适的表达式。
            var trueExpression = Expression.Constant(true);
            var result = Expression.AndAlso(trueExpression, trueExpression);
            foreach (var prop in typeof(TQueryParams).GetProperties())
            {
                var attrs = prop.GetCustomAttributes();
                foreach (BuildWhereAttribute attribute in attrs.Where(w => w is BuildWhereAttribute))
                {
                    if (string.IsNullOrWhiteSpace(attribute.Field))
                        attribute.Field = prop.Name;

                    var valueToCompareExpression = prop.GetValue(queryObject);
                    if (valueToCompareExpression is null)
                        continue;

                    // string 类型时，单独判断一次是否是空值
                    if (prop.PropertyType == typeof(string) && string.IsNullOrWhiteSpace(valueToCompareExpression as string))
                        continue;

                    var fieldExpression = Expression.Property(t, attribute.Field);
                    var valueExpression = Expression.Constant(valueToCompareExpression, prop.PropertyType);

                    // 构建表达式
                    switch(attribute)
                    {
                        case WhereEqualAttribute _:
                            result = result.AndEqualLambda(fieldExpression, valueExpression);
                            break;
                        case WhereLikeAttribute _:
                            result = result.AndLikeLambda(fieldExpression, valueExpression, buildWhereOptions.StringComparison);
                            break;
                        case WhereLargerAttribute _:
                            result = result.AndLargerLambda(fieldExpression, valueExpression);
                            break;
                        case WhereLargerEqualAttribute _:
                            result = result.AndLargerEqualLambda(fieldExpression, valueExpression);
                            break;
                        case WhereSmallerAttribute _:
                            result = result.AndSmallerLambda(fieldExpression, valueExpression);
                            break;
                        case WhereSmallerEqualAttribute _:
                            result = result.AndSmallerEqualLambda(fieldExpression, valueExpression);
                            break;
                        default:
                            throw new NotImplementedException();
                    }

                    // TODO: 增加其他的对比类型（In/Between/）
                    // TODO: 增加其他类型支持
                    // TODO: 字符串进行Equal对比的时候，增加一个 StringComparison 参数
                }
            }
            // 如：t=>t.Age.Equals(input.Age) && t.Code.Contains(input.Number) && t.Name.Contains(input.Name)
            // 注意：上方例子input.Number是因为其有特性 [WhereLike("Code")]
            return Expression.Lambda<Func<TSource, bool>>(result, t);
        }

        /// <summary>
        /// 通过sources.BuildLambda()创建表达式，减少调用方代码
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TQueryParams">对比的参数对象类型</typeparam>
        /// <param name="sources"></param>
        /// <param name="queryObject"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static IQueryable<TSource> BuildWhere<TSource, TQueryParams>(this IQueryable<TSource> sources, TQueryParams queryObject, Action<BuildWhereOptions> options = null) where TSource : class where TQueryParams : class
        {
            return sources.Where(sources.BuildLambda(queryObject, options));
        }

        /// <summary>
        /// IEnumerable资源调用BuildWhere时，转IQueryable后调用IQueryable.BuildWhere
        /// </summary>
        /// <typeparam name="TSource">集合对象类型</typeparam>
        /// <typeparam name="TQueryParams">对比的参数对象类型</typeparam>
        /// <param name="sources">集合对象</param>
        /// <param name="queryObject">请求参数对象</param>
        /// <param name="options">构建参数</param>
        /// <returns></returns>
        public static List<TSource> GetList<TSource, TQueryParams>(this IEnumerable<TSource> sources, TQueryParams queryObject, Action<BuildWhereOptions> options = null) where TSource : class where TQueryParams : class
        {
            return sources.AsQueryable().BuildWhere(queryObject, options)?.ToList();
        }

        #region BinaryExpressionExtensions

        /// <summary>
        /// 拼接Like
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <param name="stringComparison">字符串对比方式枚举</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndLikeLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression, StringComparison stringComparison = default)
        {
            // Contains 方法
            var containMethod = typeof(string).GetMethod(
                nameof(string.Contains),
                new Type[] { typeof(string), typeof(StringComparison) }
            );
            // 表达式：Contains 方法
            var containMethodExpression = Expression.Call(
                fieldExpression,
                containMethod,
                valueToCompareExpression,
                Expression.Constant(stringComparison)
            );
            return Expression.AndAlso(resultExpression, containMethodExpression);
        }

        /// <summary>
        /// 拼接Equal
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndEqualLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression)
        {
            return Expression.AndAlso(
                    resultExpression,
                    Expression.Equal(
                        fieldExpression,
                        Expression.Convert(valueToCompareExpression, fieldExpression.Type)
                    )
                );
        }

        /// <summary>
        /// 拼接Larger
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndLargerLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression)
        {
            return Expression.AndAlso(
                resultExpression,
                Expression.GreaterThan(
                    fieldExpression,
                    Expression.Convert(valueToCompareExpression, fieldExpression.Type)
                )
            );
        }

        /// <summary>
        /// 拼接LargerEqual
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndLargerEqualLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression)
        {
            return Expression.AndAlso(
                resultExpression,
                Expression.GreaterThanOrEqual(
                    fieldExpression,
                    Expression.Convert(valueToCompareExpression, fieldExpression.Type)
                )
            );
        }

        /// <summary>
        /// 拼接Smaller
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndSmallerLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression)
        {
            return Expression.AndAlso(
                resultExpression,
                Expression.LessThan(
                    fieldExpression,
                    Expression.Convert(valueToCompareExpression, fieldExpression.Type)
                )
            );
        }

        /// <summary>
        /// 拼接SmallerEqual
        /// </summary>
        /// <param name="resultExpression">结果表达式</param>
        /// <param name="fieldExpression">字段表达式</param>
        /// <param name="valueToCompareExpression">对比值表达式</param>
        /// <returns>拼接后表达式</returns>
        public static BinaryExpression AndSmallerEqualLambda(this BinaryExpression resultExpression, MemberExpression fieldExpression, ConstantExpression valueToCompareExpression)
        {
            return Expression.AndAlso(
                resultExpression,
                Expression.LessThanOrEqual(
                    fieldExpression,
                    Expression.Convert(valueToCompareExpression, fieldExpression.Type)
                )
            );
        }

        #endregion 
    }
}
